home *** CD-ROM | disk | FTP | other *** search
/ Info-Mac 4 / Info_Mac IV CD-ROM (Pacific HiTech Inc.)(August 1994).iso / Development / Source / tarsrc Folder / tape.c < prev    next >
C/C++ Source or Header  |  1994-02-02  |  14KB  |  656 lines

  1. #include "tar.h"
  2. #include <Scsi.h>
  3. #include "tape.h"
  4.  
  5. extern void WPrintf();
  6.  
  7. #define    F_OPEN    0x01
  8.  
  9. #define B_READ    0x01
  10. #define B_WRITE    0x02
  11.  
  12. #define RETRYCMD    1    /* Fake error for retry on unit attention */
  13.  
  14. Boolean            tapeDebug = false;
  15. static Boolean        lastWasWrite = false;
  16. static Boolean        fileMarkRead = false;
  17. static Boolean        noRewind = false;
  18. static Boolean        readOnly = true;
  19. static Boolean        fixed = true;
  20. static Boolean        unitAttnSeen = false;
  21. static int        flags = 0;
  22. static int        blockLen;
  23.  
  24. #define TV    pref.tapeVars
  25.  
  26. void
  27. TapeDefaults(TapeVars *tv)
  28. {
  29.     tv->unit = 5;
  30.     tv->minTO = 4;
  31.     tv->motionTO = 10;
  32.     tv->rewindTO = 180;
  33.     tv->forceVariable = false;
  34.     tv->forceModeSelect = false;
  35.     tv->densityCode = 0;
  36.     tv->speed = 0;
  37.     tv->bufMode = 0;
  38. }
  39.  
  40. void
  41. TapeBuildCmd(Byte *cmdBuf, Byte op, Byte two, int n)
  42. {
  43.     cmdBuf[0] = op;
  44.     cmdBuf[1] = two;
  45.     cmdBuf[2] = (n >> 16) & 0xff;
  46.     cmdBuf[3] = (n >> 8) & 0xff;
  47.     cmdBuf[4] = n & 0xff;
  48.     cmdBuf[5] = 0;
  49. }
  50.  
  51. static char *keyStr[] = {
  52.     "NO SENSE", "RECOVERED ERROR", "NOT READY", "MEDIUM ERROR", "HARDWARE ERROR",
  53.     "ILLEGAL REQUEST", "UNIT ATTENTION", "DATA PROTECT", "BLANK CHECK",
  54.     "VENDOR UNIQUE", "COPY ABORTED", "ABORTED COMMAND", "EQUAL", "VOLUME OVERFLOW",
  55.     "MISCOMPARE", "RESERVED"
  56. };
  57.  
  58. PrintSense(Sense *s)
  59. {
  60.     WPrintf("    valid %d class %d code %d", s->valid, s->class,
  61.             s->code);
  62.     WPrintf("    segment %d fmk %d eom %d ili %d key %x %s", s->segment,
  63.             s->fmk, s->eom, s->ili, s->key, keyStr[s->key]);
  64.     WPrintf("    info %x %x %x %x asl %d", s->info[0],
  65.             s->info[1], s->info[2], s->info[3], s->asl);
  66.     WPrintf("    asc %x ascq %x sks %x %x %x", s->asc, s->ascq, s->sks[0],
  67.             s->sks[1], s->sks[2]);
  68. }
  69.  
  70. OSErr
  71. TapeCheckSense(Sense *s)
  72. {
  73.     switch (s->key) {
  74.     case S_NOSENSE:
  75.         fileMarkRead = s->fmk;
  76.         if (s->eom) {
  77.             GenericAlert("\pEnd of tape!");
  78.             return(dskFulErr);
  79.         }
  80.  
  81.         /* residual = BYTES4_INT(s->info) * (blockLen * fixed); */
  82.         return(noErr);
  83.  
  84.     case S_MEDERROR:
  85.         GenericAlert("\pUnrecovered medium error!");
  86.         return(ioErr);
  87.  
  88.     case S_HDERROR:
  89.         GenericAlert("\pHardware error!");
  90.         return(ioErr);
  91.  
  92.     case S_VOLOVER:
  93.         GenericAlert("\pTape full!");
  94.         return(ioErr);
  95.  
  96.     case S_DATAPROTECT:
  97.         GenericAlert("\pTape is write protected!");
  98.         return(wPrErr);
  99.  
  100.     case S_NODATA:
  101.         GenericAlert("\pNo data - blank tape?");
  102.         return(ioErr);
  103.  
  104.     case S_UNITATTN:
  105.         if (unitAttnSeen)
  106.             return(ioErr);
  107.         
  108.         unitAttnSeen = true;
  109.         return(RETRYCMD);
  110.         
  111.     default:
  112.         WPrintf("tape: SCSI unhandled sense:");
  113.         PrintSense(s);
  114.         return(ioErr);
  115.         break;
  116.     }
  117.  
  118.     return(noErr);
  119. }
  120.  
  121. #define PHWT    0
  122. #define TICKS (*(unsigned int *)0x16A)
  123.  
  124. int
  125. ScsiCmd(Ptr cmdBuf, int cmdLen, Ptr dataBuf, int dataLen, int *dataSent, int flags, int timeout)
  126. {
  127.     int        cnt;
  128.     unsigned int    maxtick, t;
  129.     short        stat, msg;    
  130.     short        bus;
  131.     int        ret = 0;
  132.     OSErr        err;
  133.     SCSIInstr    si[4];
  134.     
  135.     maxtick = TICKS + timeout * 60;
  136.     while (1) {
  137.         for (cnt = 0; cnt < 4; cnt++) {
  138.             if ((err = SCSIGet()) == noErr)
  139.                 goto dosel;
  140.         
  141.             WPrintf("SCSIGet = %d %04x", err, SCSIStat());
  142.         }
  143.         
  144.         cnt = 0;
  145.         while ((bus = SCSIStat()) & 0x40) {
  146.             if ((cnt++ & 511) == 0)
  147.                 WPrintf("stat %04x", bus);
  148.                 
  149.             if ((TICKS > maxtick) || (cnt >= 0x40000)) {
  150.                 WPrintf("TapeCmd timeout free wait phase %04x cnt %d", bus, cnt);
  151.                 if ((err = SCSIComplete(&stat, &msg, 4)) != noErr) {
  152.                     WPrintf("SCSIComplete = %d", err);
  153.                 }
  154.                 
  155.                 return(-1);
  156.             }
  157.         }
  158.     }
  159.  
  160. dosel:
  161.     if ((err = SCSISelect(TV.unit)) != noErr) {
  162.         WPrintf("SCSISelect = %d st %04x", err, SCSIStat());
  163.         return(-2);
  164.     }
  165.  
  166. #if PHWT
  167.     cnt = 0;
  168.     while (1) {
  169.         bus = SCSIStat();
  170.         cnt++;
  171.         if (TICKS > maxtick) {
  172.             WPrintf("TapeCmd timeout cmd phase %04x cnt %d", bus, cnt);
  173.             if ((err = SCSIComplete(&stat, &msg, 4)) != noErr) {
  174.                 WPrintf("SCSIComplete = %d", err);
  175.             }
  176.             
  177.             return(ioErr);
  178.         }
  179.         
  180.         if ((bus & 0x7c) == 0x68)
  181.             break;    
  182.     }
  183. #endif
  184.  
  185.     if ((err = SCSICmd(cmdBuf, cmdLen)) != noErr) {
  186.         WPrintf("SCSICmd = %d st %04x", err, SCSIStat());
  187.         ret = -3;
  188.         goto docomplete;
  189.     }
  190.  
  191.     if (flags & (B_READ | B_WRITE)) {
  192.         *dataSent = 0;
  193.         if (dataLen <= blockLen) {
  194.             si[0].scOpcode = scInc;
  195.             si[0].scParam1 = dataBuf;
  196.             si[0].scParam2 = dataLen;
  197.             si[1].scOpcode = scStop;
  198.             si[1].scParam1 = 0;
  199.             si[1].scParam2 = 0;
  200.         } else {
  201.             si[0].scOpcode = scInc;
  202.             si[0].scParam1 = dataBuf;
  203.             si[0].scParam2 = blockLen;
  204.             si[1].scOpcode = scLoop;
  205.             si[1].scParam1 = -sizeof(SCSIInstr);
  206.             si[1].scParam2 = dataLen / blockLen;
  207.             si[2].scOpcode = scStop;
  208.             si[2].scParam1 = 0;
  209.             si[2].scParam2 = 0;
  210.         }
  211.  
  212. #if PHWT
  213.         cnt = 0;
  214.         while (1) {
  215.             bus = SCSIStat();
  216.             if ((cnt++ & 511) == 0)
  217.                 WPrintf("stat %04x", bus);
  218.                 
  219.             if (TICKS > maxtick) {
  220.                 WPrintf("TapeCmd timeout data phase %04x cnt %d", bus, cnt);
  221.                 if ((err = SCSIComplete(&stat, &msg, 4)) != noErr) {
  222.                     WPrintf("SCSIComplete = %d", err);
  223.                 }
  224.                 
  225.                 return(ioErr);
  226.             }
  227.             
  228.             if ((bus & 0x78) == 0x60)
  229.                 break;    
  230.         }
  231. #endif
  232.  
  233.         if (flags & B_READ) {
  234.             if ((err = SCSIRead((Ptr) si)) != noErr) {
  235.                 WPrintf("SCSIRead = %d st %04x", err, SCSIStat());
  236.                 ret = -4;
  237.             }
  238.         
  239.         } else {
  240.             if ((err = SCSIWrite((Ptr) si)) != noErr) {
  241.                 WPrintf("SCSIWrite = %d st %04x", err, SCSIStat());
  242.                 ret = -4;
  243.             }
  244.         }
  245.         
  246.         *dataSent = si[0].scParam1 - dataBuf;
  247.     }
  248.  
  249. docomplete:
  250.     if ((t = maxtick - TICKS) < 60)
  251.         t = 60;
  252.         
  253.     if ((err = SCSIComplete(&stat, &msg, t)) != noErr) {
  254.         WPrintf("SCSIComplete = %d st %04x", err, SCSIStat());
  255.         return((ret != 0) ? ret : -5);
  256.     }
  257.     
  258.     return(stat);
  259. }
  260.  
  261. OSErr
  262. TapeCmd(Ptr cmdBuf, int cmdLen, Ptr dataBuf, int dataLen, int *nMoved, int flags, int timeout)
  263. {
  264.     int    stat;    
  265.     OSErr    err;
  266.     
  267.     do {
  268.         err = noErr;
  269.         unitAttnSeen = false;
  270.         stat = ScsiCmd(cmdBuf, cmdLen, dataBuf, dataLen, nMoved, flags, timeout);
  271.         if (stat) {
  272.             int    senseMoved;
  273.             Sense    s;
  274.             Byte    senseCmd[6] = {OP_SENSE, 0, 0, 0, sizeof(s), 0};
  275.             Byte    *c = (Byte *) cmdBuf;
  276.             
  277.             WPrintf("TapeCmd: cmd %x %x %x %x %x %x stat %x",
  278.                     c[0], c[1], c[2], c[3], c[4], c[5], stat);
  279.             if (stat = ScsiCmd(senseCmd, sizeof(senseCmd), (Ptr) &s, sizeof(s),
  280.                     &senseMoved, B_READ, TV.minTO)) {
  281.                 char    buf[64];
  282.                 
  283.                 sprintf(&buf[1], "Could not get status for failed command %d",
  284.                         *(Byte *) cmdBuf);
  285.                 buf[0] = strlen(&buf[1]);
  286.                 GenericAlert(buf);
  287.                 err = ioErr;
  288.                 
  289.             } else {
  290.                 err = TapeCheckSense(&s);
  291.             }
  292.         }
  293.     } while (err == RETRYCMD);
  294.     
  295.     return(err);
  296. }
  297.  
  298.  
  299. OSErr
  300. TapeOpen(Boolean noRwd, Boolean rdOnly)
  301. {
  302.     int        t;
  303.     int        minBlockLen, maxBlockLen;
  304.     int        stat;
  305.     Sense        s;
  306.     Inquiry        inquiry;
  307.     Byte        cmd[6];
  308.     RBLData        rbl;
  309.     Byte        modeCmd[6];
  310.     ModeData    modeData;
  311.     char        buf[256];
  312.     
  313.     if (flags & F_OPEN) {
  314.         GenericAlert("\pTapeOpen called when already open!");
  315.         return(fBsyErr);
  316.     }
  317.  
  318.     lastWasWrite = false;
  319.     fileMarkRead = false;
  320.     noRewind = noRwd;
  321.     readOnly = rdOnly;
  322.     blockLen = 512; /* Must be non-zero before any commands! */
  323.     do {
  324.         s.key = S_NOTREADY;
  325.         memset(&s, 0, sizeof(s));
  326.         TapeBuildCmd(cmd, OP_READY, 0, 0);
  327.         if ((stat = ScsiCmd(cmd, sizeof(cmd), NULL, 0, &t, 0, TV.minTO)) == 0)
  328.             break;
  329.         
  330.         if (stat < 0) {
  331.             if (StopGoAlert("\pTape drive not responding"))
  332.                 return(nsDrvErr);
  333.             
  334.             continue;
  335.         }
  336.         
  337.         TapeBuildCmd(cmd, OP_SENSE, 0, sizeof(s));
  338.         if (ScsiCmd(cmd, sizeof(cmd), (Ptr) &s, sizeof(s), &t, B_READ, TV.minTO)) {
  339.             if (StopGoAlert("\pCould not read SENSE data"));
  340.                 return(nsDrvErr);
  341.                 
  342.             continue;
  343.         }
  344.         
  345.         PrintSense(&s);
  346.         if ((s.class != 7) || (s.code != 0)) {
  347.             GenericAlert("\pUnknown sense data format");
  348.             return(nsDrvErr);
  349.         }
  350.         
  351.         switch (s.key) {
  352.         case S_ILLREQ:
  353.             /* Hmm, a dumb drive. */
  354.             WPrintf("Dumb drive");
  355.             s.key = S_NOSENSE;
  356.             break;
  357.             
  358.         case S_NOTREADY:
  359.             if (StopGoAlert("\pDrive not ready"))
  360.                 return(offLinErr);
  361.             break;
  362.             
  363.         case S_NOSENSE:
  364.         case S_UNITATTN:
  365.             break;
  366.         
  367.         default:
  368.             sprintf(&buf[1], "TapeOpen unhandled sense %x", s.key);
  369.             buf[0] = strlen(&buf[1]);
  370.             GenericAlert(buf);
  371.             return(nsDrvErr);
  372.         }
  373.     } while (s.key != S_NOSENSE);
  374.     
  375.     TapeBuildCmd(cmd, OP_INQUIRY, 0, sizeof(inquiry));
  376.     if (
  377.         TapeCmd(cmd, sizeof(cmd), (Ptr) &inquiry, sizeof(inquiry), &t, B_READ, TV.minTO) ||
  378.         (t != sizeof(inquiry))
  379.     ) {
  380.         GenericAlert("\pCould not read inquiry data");
  381.         return(nsDrvErr);
  382.     }
  383.  
  384.     if ((inquiry.type != 1) && (inquiry.rmb != 1)) {
  385.         sprintf(&buf[1], "SCSI ID %d is not a tape!", TV.unit);
  386.         buf[0] = strlen(&buf[1]);
  387.         GenericAlert(buf);
  388.         return(nsDrvErr);
  389.     }
  390.  
  391.     TapeBuildCmd(cmd, OP_RBL, 0, 0);
  392.     if (
  393.         TapeCmd(cmd, sizeof(cmd), (Ptr) &rbl, sizeof(rbl), &t, B_READ, TV.minTO) ||
  394.         (t != sizeof(rbl))
  395.     ) {
  396.         WPrintf("TapeOpen: RBL failed");
  397.         /* Hmm, assume fixed 512 byte blocks, see if we get anywhere. */
  398.         minBlockLen = maxBlockLen = 512;
  399.         
  400.     } else {
  401.         minBlockLen = BYTES2_INT(rbl.minBL);
  402.         maxBlockLen = BYTES3_INT(rbl.maxBL);
  403.     }
  404.     
  405.     modeCmd[0] = OP_MODESENSE;
  406.     modeCmd[1] = modeCmd[2] = modeCmd[3] = modeCmd[5] = 0;
  407.     modeCmd[4] = sizeof(modeData);
  408.     if (TapeCmd(modeCmd, sizeof(modeCmd), (Ptr) &modeData, sizeof(modeData), &t, B_READ,
  409.             TV.minTO)) {
  410.         if (tapeDebug)
  411.             WPrintf("TapeOpen: MODE SENSE failed");
  412.  
  413.         return(nsDrvErr);
  414.     }
  415.     /*
  416.     WPrintf("TapeOpen: mode sense %x %x wp %d bm %d sp %d bdl %d",
  417.             modeData.mdl, modeData.mediumType, modeData.wp, modeData.bufMode,
  418.             modeData.speed, modeData.bdl);
  419.     WPrintf("          den %x nb %x %x %x bl %x %x %x", modeData.densityCode,
  420.             modeData.numBlocks[0], modeData.numBlocks[1], modeData.numBlocks[2],
  421.             modeData.blockLen[0], modeData.blockLen[1], modeData.blockLen[2]);
  422.     */
  423.     if (!rdOnly && modeData.wp) {
  424.         GenericAlert("\pTape is write protected!");
  425.         return(wPrErr);
  426.     }
  427.     
  428.     blockLen = BYTES3_INT(modeData.blockLen);
  429.     /*WPrintf("Block lengths: min %d max %d cur %d\n", minBlockLen, maxBlockLen, blockLen);*/
  430.     if ((blockLen == 0) || TV.forceVariable) {
  431.         /*
  432.          * Drive is in variable block mode
  433.          */
  434.         if ((pref.blockSize < minBlockLen) || (pref.blockSize > maxBlockLen)) {
  435.             sprintf(&buf[1], "Block size (%d) out drive's range (%d to %d)!",
  436.                     pref.blockSize, minBlockLen, maxBlockLen);
  437.             buf[0] = strlen(&buf[1]);
  438.             GenericAlert(buf);
  439.             return(nsDrvErr);
  440.         }
  441.         
  442.         fixed = false;
  443.         blockLen = 512; /* Fake block length */
  444.         
  445.     } else {
  446.         if ((pref.blockSize % blockLen) != 0) {
  447.             sprintf(&buf[1], "Tar block size (%d) != multiple of tape size (%d)!", 
  448.                     pref.blockSize, blockLen);
  449.             buf[0] = strlen(&buf[1]);
  450.             GenericAlert(buf);
  451.             return(nsDrvErr);
  452.         }
  453.     }
  454.  
  455.     if (TV.forceVariable || TV.forceModeSelect) {
  456.         modeCmd[0] = OP_MODESELECT;
  457.         if (!TV.forceModeSelect) {
  458.             memset(&modeData, 0, sizeof(modeData));
  459.             modeData.bufMode = TV.bufMode;
  460.             modeData.speed = TV.speed;
  461.             modeData.bdl = 8;
  462.             modeData.densityCode = TV.densityCode;
  463.         }
  464.         
  465.         if (TV.forceVariable) {
  466.             modeData.blockLen[0] =
  467.             modeData.blockLen[1] =
  468.             modeData.blockLen[2] = 0;
  469.             
  470.         } else {
  471.             modeData.blockLen[0] = (blockLen >> 16) & 0xff;
  472.             modeData.blockLen[1] = (blockLen >> 8) & 0xff;
  473.             modeData.blockLen[2] = blockLen & 0xff;
  474.         }
  475.         
  476.         if (TapeCmd(modeCmd, sizeof(modeCmd), (Ptr) &modeData, sizeof(modeData), &t,
  477.                 B_WRITE, TV.minTO)) {
  478.             GenericAlert("\pCould not send mode select to drive!");
  479.             return(nsDrvErr);
  480.         }
  481.     }
  482.  
  483.     flags = F_OPEN;
  484.     return(noErr);
  485. }
  486.  
  487. void
  488. TapeClose()
  489. {
  490.     int    n;
  491.     Byte    cmd[6];
  492.     
  493.     if (flags & F_OPEN) {
  494.         flags = 0;
  495.         if (lastWasWrite) {
  496.             n = noRewind ? 1 : 2;
  497.             TapeBuildCmd(cmd, OP_WFM, 0, n);
  498.             (void) TapeCmd(cmd, sizeof(cmd), NULL, 0, NULL, 0, TV.motionTO);
  499.         }
  500.  
  501.         if (!noRewind) {
  502.             TapeBuildCmd(cmd, OP_REWIND, IMMEDIATE, 0);
  503.             (void) TapeCmd(cmd, sizeof(cmd), NULL, 0, NULL, 0, TV.rewindTO);
  504.         }
  505.  
  506.     }
  507. }
  508.  
  509. int
  510. TapeStrategy(char *buf, int len, Boolean wrt)
  511. {
  512.     int    blocks, tout;
  513.     int    op;
  514.     int    flags;
  515.     int    nMoved;
  516.     OSErr    err;
  517.     Byte    cmd[6];
  518.  
  519.     if (len & (blockLen - 1)) {
  520.         return(ioErr);
  521.     }
  522.  
  523.     if (wrt) {
  524.         op = OP_WRITE;
  525.         flags = B_WRITE;
  526.         lastWasWrite = true;
  527.  
  528.     } else {
  529.         op = OP_READ;
  530.         flags = B_READ;
  531.         lastWasWrite = false;
  532.     }
  533.  
  534.     if (fixed) {
  535.         blocks = len;
  536.         tout = blocks /= blockLen;
  537.         TapeBuildCmd(cmd, op, FIXED, blocks);
  538.     
  539.     } else {
  540.         tout = len / blockLen;
  541.         TapeBuildCmd(cmd, op, VARIABLE, len);
  542.     }
  543.     
  544.     if ((err = TapeCmd(cmd, sizeof(cmd), buf, len, &nMoved, flags, TV.motionTO + tout))
  545.             != noErr) {
  546.         char    buf[64];
  547.         
  548.         sprintf(&buf[1], "Tape %s error %d", (flags == B_READ) ? "read" : "write", err);
  549.         buf[0] = strlen(&buf[1]);
  550.         GenericAlert(buf);
  551.         return(err);
  552.     }
  553.     
  554.     return(nMoved);
  555. }
  556.  
  557. int
  558. TapeRead(char *buf, int len)
  559. {
  560.     if (flags & F_OPEN) {
  561.         if (fileMarkRead) {
  562.             fileMarkRead = false;
  563.             return(0);
  564.         }
  565.  
  566.         return(TapeStrategy(buf, len, false));
  567.     }
  568.     
  569.     return(notOpenErr);
  570. }
  571.  
  572. int
  573. TapeWrite(char *buf, int len)
  574. {
  575.     if (flags & F_OPEN)
  576.         return(TapeStrategy(buf, len, true));
  577.     
  578.     return(notOpenErr);
  579. }
  580.  
  581. #ifdef notdef
  582. int
  583. TapeIoctl(dev, cmd, addr, mode)
  584. dev_t    dev;
  585. int    cmd;
  586. caddr_t    addr;
  587. int    mode;
  588. {
  589.     int        ret;
  590.     int        timeout;
  591.     struct mtop    *mtop;
  592.     Byte        cmd[6];
  593.  
  594.     if (!(flags & F_OPEN))
  595.         return(ENXIO);
  596.  
  597.     switch (cmd) {
  598.     case MTIOCTOP:
  599.         mtop = (struct mtop *)addr;
  600.         switch (mtop->mt_op) {
  601.         case MTWEOF:
  602.             TapeBuildCmd(cmd, OP_WFM, 0, mtop->mt_count);
  603.             timeout = TV.motionTO;
  604.             break;
  605.  
  606.         case MTFSF:
  607.             TapeBuildCmd(cmd, OP_SPACE, FILEMARKS, mtop->mt_count);
  608.             timeout = TV.rewindTO;
  609.             break;
  610.  
  611.         case MTFSR:
  612.             TapeBuildCmd(cmd, OP_SPACE, BLOCKS, mtop->mt_count);
  613.             timeout = TV.rewindTO;
  614.             break;
  615.  
  616.         case MTBSF:
  617.             TapeBuildCmd(cmd, OP_SPACE, EOD, 0);
  618.             timeout = TV.rewindTO;
  619.             break;
  620.             
  621.         case MTREW:
  622.             TapeBuildCmd(cmd, OP_REWIND, 0, 0);
  623.             timeout = TV.rewindTO;
  624.             break;
  625.  
  626.         case MTOFFL:
  627.             TapeBuildCmd(cmd, OP_REWIND, IMMEDIATE, 0);
  628.             timeout = TV.rewindTO;
  629.             break;
  630.  
  631.         case MTRETEN:
  632.             TapeBuildCmd(cmd, OP_PREWIND, 0, 0);
  633.             timeout = TV.rewindTO * 2;
  634.             break;
  635.             
  636.         case MTFORMAT:
  637.             TapeBuildCmd(cmd, OP_ERASE, 1, 0);
  638.             timeout = TV.rewindTO * 2;
  639.             break;
  640.             
  641.         default:
  642.             return(EINVAL);
  643.         }
  644.  
  645.         ret = TapeCmd(cmd, sizeof(cmd), NULL, 0, NULL, 0, timeout);
  646.         break;
  647.  
  648.     default:
  649.         ret = EINVAL;
  650.         break;
  651.     }
  652.  
  653.     return(ret);
  654. }
  655. #endif
  656.